require File.dirname(__FILE__)+"/../../test_helper"
require "starframe/sprite/collision"

class StarFrame::Sprite::Collision
  attr_accessor :target, :method_name, :options
  class Builder
    attr_accessor :target, :name, :method_name, :collision, :options
  end
end

class TestStarFrameSpriteCollision < Test::Unit::TestCase
  class CollisionObject
    attr_accessor :other
    def collide other
      @other = other
    end
  end
  
  # Collision::Builder#initialize
  def test_builder_initialize
    collision_builder = StarFrame::Sprite::Collision::Builder.new(CollisionObject, :collision, TrueCollision, :options)
    assert_equal CollisionObject, collision_builder.target
    assert_equal :collision,      collision_builder.name
    assert_nil                    collision_builder.method_name
    assert_equal TrueCollision,   collision_builder.collision
    assert_equal [:options],      collision_builder.options
  end
  def test_builder_initialize_with_block
    collision_builder = StarFrame::Sprite::Collision::Builder.new(CollisionObject, :collision, TrueCollision, :options){ |other| }
    assert_equal CollisionObject,                                   collision_builder.target
    assert_equal :collision,                                        collision_builder.name
    assert_equal :_starframe_sprite_collision__collision_collision, collision_builder.method_name
    assert_equal TrueCollision,                                     collision_builder.collision
    assert_equal [:options],                                        collision_builder.options
    
    collision_object = CollisionObject.new
    assert_respond_to collision_object, :_starframe_sprite_collision__collision_collision
  end
  
  # Collision::Builder#build
  def test_builder_build
    collision_builder = StarFrame::Sprite::Collision::Builder.new(CollisionObject, :collision, TrueCollision, :options){ |other| }
    collision_object = CollisionObject.new
    
    collision = collision_builder.build(collision_object)
    assert_instance_of TrueCollision, collision
    
    assert_equal collision_object,                                  collision.target
    assert_equal :_starframe_sprite_collision__collision_collision, collision.method_name
    assert_equal [:options],                                        collision.options
  end
  
  # Collisionのサブクラスのテスト
  class NoneCollision < StarFrame::Sprite::Collision
    # 何も定義されていないので相手に依存する
  end
  class TrueCollision < StarFrame::Sprite::Collision
    # TrueCollisionと判定した場合のみtrueを返す
    # それ以外は相手に依存する
    def collide? other
      case other
      when TrueCollision
        true
      else
        super
      end
    end
  end
  class FalseCollision < StarFrame::Sprite::Collision
    # TrueCollisionと判定した場合trueを返す
    # FalseCollisionと判定した場合falseを返す
    # それ以外は相手に依存する
    def collide? other
      case other
      when TrueCollision
        true
      when FalseCollision
        false
      else
        super
      end
    end
  end
  class TestCollision < StarFrame::Sprite::Collision
    # 相手に依存された場合trueを、されていない場合falseを返す
    def collide? other
      @dispatched
    end
  end
  
  # Collision#collide?
  def test_collides
    object = CollisionObject.new
    test_collision  = TestCollision.new  object, :test
    none_collision  = NoneCollision.new  object, :none
    true_collision  = TrueCollision.new  object, :true
    false_collision = FalseCollision.new object, :false
    [
      [test_collision,  test_collision,  nil,   nil],
      [test_collision,  none_collision,  nil,   true],
      [test_collision,  true_collision,  nil,   true],
      [test_collision,  false_collision, nil,   true],
      
      [none_collision,  none_collision,  nil,   nil],
      [none_collision,  true_collision,  nil,   nil],
      [none_collision,  false_collision, nil,   nil],
      
      [true_collision,  true_collision,  true,  true],
      [true_collision,  false_collision, true,  true],
      
      [false_collision, false_collision, false, false],
    ].each do |collition1, collition2, result_1to2, result_2to1 |
      assert_equal result_1to2, collition1.collide?(collition2), "#{collition1} -> #{collition2}"
      assert_equal result_2to1, collition2.collide?(collition1), "#{collition2} -> #{collition1}"
    end
  end
  
  def test_collide_true
    object1 = CollisionObject.new
    object2 = CollisionObject.new
    
    collision1 = TrueCollision.new object1, :collide
    collision2 = TrueCollision.new object2, :collide
    
    assert_equal true, collision1.collide(collision2)
    assert_equal object2, object1.other
    assert_equal object1, object2.other
  end
  def test_collide_false
    object1 = CollisionObject.new
    object2 = CollisionObject.new
    
    collision1 = FalseCollision.new object1, :collide
    collision2 = FalseCollision.new object2, :collide
    
    assert_equal false, collision1.collide(collision2)
    assert_nil object1.other
    assert_nil object2.other
  end
  def test_collide_nil
    object1 = CollisionObject.new
    object2 = CollisionObject.new
    
    collision1 = TrueCollision.new object1, :collide
    collision2 = TrueCollision.new object2, nil
    
    assert_equal true, collision1.collide(collision2)
    assert_equal object2, object1.other
    assert_nil            object2.other
  end
  
  # Collision#position
  def test_position
    object1 = CollisionObject.new
    class << object1
      attr_accessor :x, :y
    end
    object1.x = 1
    object1.y = 2
    
    collision1 = TrueCollision.new object1, nil
    class << collision1
      attr_accessor :x, :y
      public :position
    end
    collision1.x = 1
    collision1.y = 2
    
    x, y = collision1.position
    
    assert_equal 2, x
    assert_equal 4, y
  end
  
  # Collision class methods
  def test_dot_in_rect
    -1.step(1, 0.1) do |x|
      -1.step(1, 0.1) do |y|
        result = ((0..1) === x && (0..1) === y)
        assert_equal result, StarFrame::Sprite::Collision.dot_in_rect?([x, y], [[0, 0], [1, 1]])
      end
    end
  end
  
  def test_dot_in_circle
    -1.step(1, 0.1) do |x|
      -1.step(1, 0.1) do |y|
        result = ((x**2 + y**2) <= 1)
        assert_equal result, StarFrame::Sprite::Collision.dot_in_circle?([x, y], [[0, 0], 1])
      end
    end
  end
end
